#include <fhe/core/Node.h>
#include <fhe/core/NodeFactory.h>
#include <fhe/core/Registerers.h>
#include <fhe/math/Vec.h>
#include <algorithm>
#include <sstream>

namespace boost
{
  void intrusive_ptr_add_ref( fhe::core::Node* node )
  {
    node->refCount_++;
  }
  
  void intrusive_ptr_release( fhe::core::Node* node )
  {
    if ( --node->refCount_ == 0 )
    {
      delete node;
    }
  }
}

namespace fhe
{
  namespace core
  {
    FHE_NODE( core, Node );
  
    Node::Node()
      : refCount_( 0 )
      , parent_( 0 )
    {
    }
    
    Node::~Node()
    {
    }
    
    std::string Node::module() const
    {
      return module_;
    }
    
    void Node::setModule( const std::string& module )
    {
      module_ = module;
    }
    
    std::string Node::type() const
    {
      return type_;
    }
    
    bool Node::is( const std::string& type ) const
    {
      return type == type_ || supers_.find( type ) != supers_.end();
    }
    
    void Node::setType( const std::string& type )
    {
      type_ = type;
    }
    
    Node::ChildIterator Node::childrenBegin() const
    {
      return children_.begin();
    }
    
    Node::ChildIterator Node::childrenEnd() const
    {
      return children_.end();
    }
    
    Node::Ptr Node::parent() const
    {
      return parent_;
    }
    
    Node::Ptr Node::root() const
    {
      return parent_ ? parent_->root() : const_cast<Node*>( this );
    }
    
    void Node::attachToParent( const Ptr& parent )
    {
      if ( parent_ != parent.get() )
      {
        detachFromParent();
        parent_ = parent.get();
        if ( parent_ )
        {
          parent_->addChild( this );
        }
      }
    }
    
    void Node::detachFromParent()
    {
      if ( parent_ )
      {
        Node* parent = parent_;
        parent_ = 0;
        parent->removeChild( this );
      }
    }
    
    void Node::removeChild( const Ptr& child )
    {
      for ( std::vector< Ptr >::iterator i = children_.begin(); i != children_.end(); ++i )
      {
        if ( *i == child )
        {
          children_.erase( i );
          child->detachFromParent();
          return;
        }
      }
    }
    
    void Node::addChild( const Ptr& child )
    {
      if ( !hasChild( child ) )
      {
        children_.push_back( child );
        child->attachToParent( this );
      }
    }
    
    bool Node::hasChild( const Ptr& child ) const
    {
      for ( std::vector< Ptr >::const_iterator i = children_.begin(); i != children_.end(); ++i )
      {
        if ( *i == child )
        {
          return true;
        }
      }
      return false;
    }
    
    bool Node::hasFunc( const std::string& name ) const
    {
      return funcs_.find( name ) != funcs_.end();
    }
    
    IFunc::Ptr Node::getFunc( const std::string& name ) const
    {
      std::map< std::string, IFunc::Ptr >::const_iterator i = funcs_.find( name );
      FHE_ASSERT_MSG( i != funcs_.end(), "unknown func %s", name.c_str() );
      return i->second;
    }
    
    void Node::addFunc( const IFunc::Ptr& func )
    {
      FHE_ASSERT_MSG( !hasFunc( func->name() ), "duplicate func %s", func->name().c_str() );
      funcs_[func->name()] = func;
    }
    
    bool Node::hasVar( const std::string& name ) const
    {
      return vars_.find( name ) != vars_.end();
    }
    
    IVar::Ptr Node::getVar( const std::string& name ) const
    {
      std::map< std::string, IVar::Ptr >::const_iterator i = vars_.find( name );
      FHE_ASSERT_MSG( i != vars_.end(), "unknown var %s", name.c_str() );
      return i->second;
    }
    
    void Node::addVar( const IVar::Ptr& var )
    {
      FHE_ASSERT_MSG( !hasVar( var->name() ), "duplicate var %s in node %s", var->name().c_str(), type().c_str() );
      vars_[var->name()] = var;
    }
    
    void Node::set( const std::string& name, Val v )
    {
      FHE_ASSERT_MSG( hasVar( name ), "unknown var %s", name.c_str() );
      getVar( name )->set( v );
    }
    
    Val Node::get( const std::string& name ) const
    {
      if ( hasVar( name ) )
      {
        return getVar( name )->get();
      }
      else if ( hasFunc( name ) )
      {
        return const_cast<Node*>(this)->call( name );
      }
      else
      {
        FHE_ERROR( "unknown var %s", name.c_str() );
      }
    }
    
    Node::FuncIterator Node::funcsBegin() const
    {
      return funcs_.begin();
    }
    
    Node::FuncIterator Node::funcsEnd() const
    {
      return funcs_.end();
    }
    
    Node::VarIterator Node::varsBegin() const
    {
      return vars_.begin();
    }
    
    Node::VarIterator Node::varsEnd() const
    {
      return vars_.end();
    }
    
    void Node::addSuper( const std::string& super )
    {
      supers_.insert( super );
    }
    
    Node::Ptr Node::load( const std::string& filename )
    {
      return load( FileSystem::instance().load( filename ) );
    }
    
    Node::Ptr Node::load( const Data::Ptr& data )
    {
      FHE_ASSERT_MSG( data->is<std::string>( "module" ),
                      "node data must have module" );
      FHE_ASSERT_MSG( data->is<std::string>( "type" ),    
                      "node data must have type" );
      Node::Ptr node = NodeFactory::instance().buildNode( 
        data->get<std::string>( "module" ), 
        data->get<std::string>( "type" ) );
      
      if ( data->hasChild( "vars" ) )
      {
        const Data::Ptr vars = data->getChild( "vars" );
        for ( Data::ChildIterator var = vars->childrenBegin();
              var != vars->childrenEnd(); ++var )
        {
          FHE_ASSERT_MSG( (*var)->is<std::string>( "name" ),
                          "var data must have name" );
          const std::string name = (*var)->get<std::string>( "name" );
          FHE_ASSERT_MSG( node->hasVar( name ),
                          "unknown var %s", name.c_str() );
          
          FHE_ASSERT_MSG( (*var)->is<std::string>( "type" ),
                          "var data must have type" );
          const std::string type = (*var)->get<std::string>( "type" );
          FHE_ASSERT_MSG( FileSystem::instance().hasSerializer( type ),
                          "unknown var type %s", type.c_str() );
          
          FHE_ASSERT_MSG( (*var)->hasChild( "value" ),
                          "var data must have value" );
          
          const ISerializer::Ptr serializer = FileSystem::instance().getSerializer( type );
          const Val val = serializer->deserialize( (*var)->getChild( "value" ) );
          node->getVar( name )->set( val );
        }
      }
      
      if ( data->hasChild( "children" ) )
      {
        const Data::Ptr children = data->getChild( "children" );
        for ( Data::ChildIterator child = children->childrenBegin();
              child != children->childrenEnd(); ++child )
        {
          node->addChild( load( *child ) );
        }
      }
      
      if ( node->hasFunc( "run" ) )
      {
        node->call( "run" );
      }
      
      return node;
    }
    
    void Node::save( const std::string& filename ) const
    {
      FileSystem::instance().save( save(), filename );
    }
    
    Data::Ptr Node::save() const
    {
      Data::Ptr data( new Data );
      data->setName( "node" );
      data->set<std::string>( "module", module() );
      data->set<std::string>( "type", type() );
      
      if ( !vars_.empty() )
      {
        Data::Ptr varsData = data->addChild( "vars" );
        
        for ( VarIterator var = vars_.begin(); var != vars_.end(); ++var )
        {
          const Val val = var->second->get();
          if ( FileSystem::instance().hasSerializer( val.typeInfo() ) )
          {
            const ISerializer::Ptr serializer = FileSystem::instance().getSerializer( val.typeInfo() );
            const Data::Ptr varData = varsData->addChild( "var" );
            varData->set<std::string>( "name", var->first );
            varData->set<std::string>( "type", serializer->name() );
            serializer->serialize( varData->addChild( "value" ), val );
          }
        }
      }
      
      if ( !children_.empty() )
      {
        Data::Ptr childrenData( new Data );
        childrenData->setName( "children" );
        data->addChild( childrenData );
        
        for ( ChildIterator child = children_.begin(); child != children_.end(); ++child )
        {
          childrenData->addChild( (*child)->save() );
        }
      }
      
      return data;
    }
  }
}
